/***********************************************************************************************************************
 * ############################################# Application AdewebLite #############################################
 * AdewebLite Android est une application permettant de consulter des emplois du temps fournis par l'université UHP
 * avec ou sans connexion internet (informations gardées en mémoire à chaque synchronisation). Et utilisant la web
 * API ADE pour collecter les données.
 * @author Andrianjafintrimo Victor, Zanette Sébastien
 * 
 * Basée sur l'application du même nom, utilisant le flux RSS (données à traiter), de :
 * @author Schnell Michaël, Zanette Sébastien
 * **********************************************************************************************************************
 */

package ade.schedule;

import ade.database.Database;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;

import ade.exception.AAAException;
import android.os.AsyncTask;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Tâche asynchrone de mise à jour d'un emploi du temps.
 * @author Zanette Sébastien, @author Andrianjafintrimo Victor
 */
public class UpdateManager extends AsyncTask<Void, Object, Void> {

    public static final int STATUS_OTHER = 0;
    public static final int STATUS_NOT_UPDATED = 1;
    public static final int STATUS_WAITING_FOR_UPDATE = 2;
    public static final int STATUS_UPDATING = 3;
    public static final int STATUS_UPDATED = 4;
    private static UpdateManager oldInstance;
    private static UpdateManager instance = new UpdateManager();
    protected ArrayList<UpdateListener> listeners;
    protected ArrayList<String> queue;
    protected ArrayList<String[]> nameChange;
    protected Hashtable<String, Integer> status;

    /**
     * 
     */
    public UpdateManager() {
        this(new ArrayList<UpdateListener>(), new ArrayList<String>(), new ArrayList<String[]>(), new Hashtable<String, Integer>());
        this.initIconSchedule();
    }

    /**
     * Constructeur.
     * @param um 
     */
    public UpdateManager(UpdateManager um) {
        this(um.listeners, um.queue, um.nameChange, um.status);
    }

    /**
     * Constructeur.
     * @param listeners
     * @param queue
     * @param nameChange 
     * @param status
     */
    public UpdateManager(ArrayList<UpdateListener> listeners,
            ArrayList<String> queue, ArrayList<String[]> nameChange,
            Hashtable<String, Integer> status) {
        super();
        this.listeners = listeners;
        this.queue = queue;
        this.nameChange = nameChange;
        this.status = status;
        this.execute();
    }

    /**
     * Ajoute un écouteur.
     * @param listener 
     */
    public void addListener(UpdateListener listener) {
        this.listeners.add(listener);
    }

    /**
     * Retire un écouteur.
     * @param listener 
     */
    public void removeListener(UpdateListener listener) {
        this.listeners.remove(listener);
    }

    /**
     * Retourne l'instance de <code>UpdateManager</code>.
     * @return instance de <code>UpdateManager</code>
     */
    public static UpdateManager getInstance() {
        return UpdateManager.instance;
    }

    /**
     * Renouvelle l'instance de <code>UpdateManager</code>.
     */
    public static void resume() {
        UpdateManager.instance = new UpdateManager(UpdateManager.oldInstance);
        UpdateManager.oldInstance = null;
    }

    /**
     * Permet de temporiser les actions de la tâche asynchrone de 500 ms.
     */
    protected static void sleep() {
        // Temporisation de 500ms
        try {
            Thread.sleep(500);
        } catch (InterruptedException e1) {
            long start = System.currentTimeMillis(),
                    current = System.currentTimeMillis();
            while (current - start < 500) {
                current = System.currentTimeMillis();
            }
        }// try
    }

    /**
     * Prend le 1er emploi du temps de la liste à synchroniser, et synchronise l'emploi du temps.
     * (Récupération des cours, comparaison, insertions des modifications).
     * @param params
     * @return 
     */
   @Override
	protected Void doInBackground(Void... params) {
               
		while (this.listeners.isEmpty()) UpdateManager.sleep() ;
		int oldQueueSize = this.queue.size() ;
		while (!this.listeners.isEmpty()) {
			UpdateManager.sleep() ;
			
			if (this.queue.size() != oldQueueSize) {
				oldQueueSize = this.queue.size() ;
				this.publishProgress("unknown", "unknown") ;
			}// if
			
			for(String[] nameChange : this.nameChange) {
				this.publishProgress(nameChange[0], this.status.get(nameChange[0]), null, nameChange[1]) ;
			}// for
			
			for(Iterator<String> iter = this.queue.iterator(); iter.hasNext(); ) {
				String schedule = iter.next() ;
				try {					
					this.setScheduleStatus(schedule, UpdateManager.STATUS_UPDATING) ;
					Database db = new Database() ; 
					Schedule newS = db.getScheduleWithName(schedule) ;
                                        newS.setStudentNumList();
					newS.build() ;
					Schedule oldS = db.getScheduleWithName(schedule) ;
					oldS.buildComplete(db) ;
					db.setLastModifSchedule(oldS.getName()) ;
					ScheduleUpdater updater = new GuidScheduleUpdater() ;
					updater.compare(oldS, newS) ;
					updater.updateSchedule() ;
					this.setScheduleStatus(schedule, UpdateManager.STATUS_UPDATED) ;
				} catch (AAAException e) {
					this.setScheduleStatus(schedule, UpdateManager.STATUS_NOT_UPDATED, e) ;
				}// try
				iter.remove() ;
			}// for
		}// while
		UpdateManager.oldInstance = UpdateManager.instance ;
		UpdateManager.instance = null ;
		return null ;
	}

    /**
     * Annulation de la tâche asynchrone.
     */
    @Override
    protected void onCancelled() {
        super.onCancelled();
    }

    /**
     * Resultat de la tâche asynchrone.
     * @param result 
     */
    @Override
    protected void onPostExecute(Void result) {
        super.onPostExecute(result);
    }

    /**
     * 
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * 
     * @param values 
     */
    @Override
    protected void onProgressUpdate(Object... values) {
        for (UpdateListener listener : this.listeners) {
            listener.onUpdate(values);
        }// for
    }

    /**
     * Retourne le statut de synchronisation d'un emploi du temps.
     * @param scheduleName
     * @return entier représentant un statut
     */
    public int getScheduleStatus(String scheduleName) {
        if (this.status.containsKey(scheduleName)) {
            return this.status.get(scheduleName);
        }// if
        return UpdateManager.STATUS_OTHER;
    }

    /**
     * Positionne le statut de synchronisation d'un emploi du temps.
     * @param scheduleName emploi du temps
     * @param newStatus statut de synchronisation
     * @param e Exception interne de l'application
     */
    public void setScheduleStatus(String scheduleName, int newStatus, AAAException e) {
        this.status.put(scheduleName, newStatus);
        this.publishProgress(scheduleName, newStatus, e);
    }

    /**
     * Positionne le statut de synchronisation d'un emploi du temps.
     * @param scheduleName emploi du temps
     * @param newStatus statut de synchronisation
     */
    public void setScheduleStatus(String scheduleName, int newStatus) {
        this.setScheduleStatus(scheduleName, newStatus, null);
    }

    /**
     * Ajoute l'emploi du temps à la fin de la liste d'emploi du temps à synchroniser.
     * @param scheduleName emploi du temps
     */
    public void addQueue(String scheduleName) {
        this.status.put(scheduleName, UpdateManager.STATUS_WAITING_FOR_UPDATE);
        this.queue.add(scheduleName);
    }

    /**
     * 
     * @param scheduleName
     * @param newName 
     */
    public void addNameChange(String scheduleName, String newName) {
        this.nameChange.add(new String[]{scheduleName, newName});
    }

    /**
     * Retourne la table de correspondance Nom de l'emploi du temps / Statut
     * @return table de correspondance emploi du temps / Statut
     */
    public Hashtable<String, Integer> getStatusC() {
        return status;
    }

    /**
     * Initialise l'icone de statut de synchronisation au démarage de l'application.
     */
    private void initIconSchedule() {
        Database db = new Database();
        String[] scheduleNames = db.getSchedulesName();
        for (String name : scheduleNames) {
            try {
                Schedule s = db.getScheduleWithName(name);
                if (s.getStatus() == 1 || s.getLastModif()==null || s.getLastModif().equals("null")) { //alors l'edt n'a jamais été synchronisé
                    this.status.put(name, UpdateManager.STATUS_NOT_UPDATED);
                } else { //si l'emploi du temps ea été synchronisé les 3 derniers jours il est considéré comme à jour
                    if (!s.getLastModif().equals("null")) {
                        GregorianCalendar calendar = new java.util.GregorianCalendar();
                        //calendar.add(Calendar.DATE, 3); //on increment la date de 3 jours
                        String currentDate = new SimpleDateFormat("MM/dd/yyyy").format(calendar.getTime());
                        String lastModifDate = s.getLastModif();
                        Calendar modif = this.toCalendar(lastModifDate, "yyyy/MM/dd");
                        modif.add(Calendar.DAY_OF_MONTH, 3);
                        lastModifDate = new SimpleDateFormat("MM/dd/yyyy").format(modif.getTime());
                        //DateFormat df = new DateFormat();
                        Date d1 = new Date(currentDate);
                        Date d2 = new Date(lastModifDate);
                        if (d1.before(d2)) //si d1<d2
                        {
                            this.status.put(name, UpdateManager.STATUS_UPDATED);
                        }
                    }
                }// for
            } catch (AAAException ex) {
                Logger.getLogger(UpdateManager.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    
    /**
     * Convertit un type String en type Calendar.
     * @param dateString chaine
     * @param pattern format de la date dans la chaine
     * @return 
     */
    public Calendar toCalendar(String dateString, String pattern) {
        try {
           SimpleDateFormat format = new SimpleDateFormat(pattern);
           Date date = format.parse(dateString);
           Calendar calendar = Calendar.getInstance();
           calendar.setTime(date);
           return calendar;
        } catch (java.text.ParseException ex) {
            Logger.getLogger(UpdateManager.class.getName()).log(Level.SEVERE, null, ex);
            return new java.util.GregorianCalendar();
        }
    }
    
}
